package deploy

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo/options"
	appsv1 "k8s.io/api/apps/v1"
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"strings"
	"time"
	"twwch/k8s-admin/apis"
	"twwch/k8s-admin/application/base"
	serviceSvc "twwch/k8s-admin/application/service"
	deployRepo "twwch/k8s-admin/component/repo/deploy"
	"twwch/k8s-admin/models/common"
	"twwch/k8s-admin/models/deploy"
	svcPb "twwch/k8s-admin/models/service"
)

type DeployService struct {
	base.BaseService
	svcService *serviceSvc.ServiceService
}

var svc = &DeployService{}

func GetService() *DeployService {
	svc.Logger = log.WithField("service", "deploy")
	svc.svcService = serviceSvc.GetService()
	return svc
}

func (svc *DeployService) getDeployName(name string) string {
	if strings.HasSuffix(name, "-deploy") {
		return name
	}
	return fmt.Sprintf("%v-deploy", name)
}

func (svc *DeployService) CreateDeploy(ctx context.Context, req *deploy.CreateReq) (*deploy.CreateResp, error) {
	deployName := svc.getDeployName(req.Name)
	resp := &deploy.CreateResp{
		Deployment: &deploy.CreateDeployInfo{
			IsOk: false,
		},
		Service: &svcPb.CreateResp{
			IsOk: false,
		},
		IsCreateSvc: req.IsCreateSvc,
	}
	if count, err := deployRepo.GetRepoInstance().GetBase().Count(ctx, bson.M{
		"deploy_name": deployName,
		"namespace":   req.Namespace,
	}); count > 0 || err != nil {
		if count > 0 {
			return nil, errors.New(deployName + "已存在")
		}
		svc.Logger.Error(ctx, err)
		return resp, err
	}
	//https://github.com/kubernetes/client-go/blob/master/examples/create-update-delete-deployment/main.go
	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deployName,
			Namespace: req.Namespace,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: svc.Int32Ptr(req.Replicas),
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": deployName,
				},
			},
			Template: apiv1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": deployName,
					},
				},
				Spec: apiv1.PodSpec{
					Containers: req.Containers,
					Volumes:    req.Volumes,
				},
			},
		},
	}
	result, err := apis.AppsV1().Deployments(req.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
	if err != nil {
		svc.Logger.Error(ctx, err)
		return resp, err
	}
	svc.Logger.Infof("Created deployment %q.\n", result.GetObjectMeta().GetName())
	resp = &deploy.CreateResp{
		Deployment: &deploy.CreateDeployInfo{
			Name: result.GetName(),
			Info: result,
			IsOk: true,
		},
	}
	_, err = deployRepo.GetRepoInstance().GetBase().UpdateOne(ctx,
		bson.M{
			"deploy_name": deployName,
			"namespace":   req.Namespace,
		},
		bson.M{
			"$set": bson.M{
				"deployment": deployment,
			},
			"$setOnInsert": bson.M{
				"create_time": time.Now(),
			},
		}, options.Update().SetUpsert(true))
	if err != nil {
		svc.Logger.Error(ctx, err)
	}

	if req.IsCreateSvc {
		svcReq := &svcPb.CreateReq{
			ReqBase: svcPb.ReqBase{
				Name:      req.Name,
				Namespace: req.Namespace,
			},
			Type:     req.Service.Type,
			Selector: map[string]string{"app": deployName},
			Ports:    req.Service.Ports,
		}
		svcResp, err := svc.svcService.Create(ctx, svcReq)
		if err != nil {
			svc.Logger.Error(ctx, err)
			return resp, err
		}
		resp.Service = svcResp
	}
	return resp, nil
}

func (svc *DeployService) DeleteDeploy(ctx context.Context, req *deploy.DeleteReq) (*common.Empty, error) {
	deployName := svc.getDeployName(req.Name)
	if count, err := deployRepo.GetRepoInstance().GetBase().Count(ctx, bson.M{
		"deploy_name": deployName,
		"namespace":   req.Namespace,
	}); count == 0 || err != nil {
		if count == 0 {
			return nil, errors.New(deployName + "不存在")
		}
		svc.Logger.Error(ctx, err)
		return nil, err
	}
	err := apis.AppsV1().Deployments(req.Namespace).Delete(ctx, deployName, metav1.DeleteOptions{})
	if err != nil {
		svc.Logger.Error(ctx, err)
		return nil, err
	}
	_, err = deployRepo.GetRepoInstance().GetBase().DeleteOne(ctx, bson.M{"deploy_name": deployName, "namespace": req.Namespace})
	if err != nil {
		svc.Logger.Error(ctx, err)
	}
	return nil, nil
}

func (svc *DeployService) UpdateDeploy(ctx context.Context, req *deploy.UpdateReq) (*deploy.UpdateResp, error) {
	deployName := svc.getDeployName(req.Name)
	if count, err := deployRepo.GetRepoInstance().GetBase().Count(ctx, bson.M{
		"deploy_name": deployName,
		"namespace":   req.Namespace,
	}); count == 0 || err != nil {
		if count == 0 {
			return nil, errors.New(deployName + "不存在")
		}
		svc.Logger.Error(ctx, err)
		return nil, err
	}
	resp := &deploy.UpdateResp{
		Deployment: &deploy.CreateDeployInfo{
			IsOk: false,
		},
	}
	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deployName,
			Namespace: req.Namespace,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: svc.Int32Ptr(req.Replicas),
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": deployName,
				},
			},
			Template: apiv1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": deployName,
					},
				},
				Spec: apiv1.PodSpec{
					Containers: req.Containers,
					Volumes:    req.Volumes,
				},
			},
		},
	}
	result, err := apis.AppsV1().Deployments(req.Namespace).Update(ctx, deployment, metav1.UpdateOptions{})
	if err != nil {
		svc.Logger.Error(ctx, err)
		return resp, err
	}
	_, err = deployRepo.GetRepoInstance().GetBase().UpdateOne(ctx, bson.M{"deploy_name": deployName, "namespace": req.Namespace}, bson.M{
		"$set": bson.M{
			"deployment":  deployment,
			"update_time": time.Now(),
		},
	})
	if err != nil {
		svc.Logger.Error(ctx, err)
	}
	resp = &deploy.UpdateResp{
		Deployment: &deploy.CreateDeployInfo{
			Name: result.GetName(),
			Info: result,
			IsOk: true,
		},
	}
	return resp, nil
}

func (svc *DeployService) GetDeploy(ctx context.Context, req *deploy.GetReq) (*deploy.GetResp, error) {
	deployName := svc.getDeployName(req.Name)
	if count, err := deployRepo.GetRepoInstance().GetBase().Count(ctx, bson.M{
		"deploy_name": deployName,
		"namespace":   req.Namespace,
	}); count == 0 || err != nil {
		if count == 0 {
			return nil, errors.New(deployName + "不存在")
		}
		svc.Logger.Error(ctx, err)
		return nil, err
	}
	result, err := apis.AppsV1().Deployments(req.Namespace).Get(ctx, deployName, metav1.GetOptions{})
	if err != nil {
		svc.Logger.Error(ctx, err)
		return nil, err
	}
	resp := &deploy.GetResp{
		Deployment: result,
	}
	return resp, nil
}

func (svc *DeployService) ListDeploy(ctx context.Context, req *deploy.ListReq) (*deploy.ListResp, error) {
	filters := bson.M{}
	if req.Namespace != "" {
		filters["namespace"] = req.Namespace
	}
	if req.Name != "" {
		filters["deploy_name"] = bson.M{"$regex": req.Name, "$options": "$i"}
	}
	ops := options.Find().SetSort(bson.M{"namespace": 1})
	deploys := make([]*deploy.Deploy, 0)
	err := deployRepo.GetRepoInstance().GetBase().Find(ctx, filters, &deploys, ops)
	if err != nil {
		svc.Logger.Error(ctx, err)
		return nil, err
	}
	count, err := deployRepo.GetRepoInstance().GetBase().Count(ctx, filters)
	if err != nil {
		svc.Logger.Error(ctx, err)
		return nil, err
	}
	return &deploy.ListResp{
		Total: count,
		Items: deploys,
	}, nil
}
